/* This class keeps information for a DKS node.
 * It keeps:
 *	DKSNetAddress: IP, Port
 *      DKSOverlayAddress: ID, GUID, R_GUID
 */
package org.kth.dks.dks_comm;

import java.net.MalformedURLException;
import java.util.Random;
import java.util.regex.Pattern;


public class DKSRef {
  DKSNetAddress netAddress = null;
  DKSOverlayAddress overlayAddress = null;
  long nonce;

  private static final Pattern dksUrl =
      Pattern.compile("^ *?dks://(.*?):(\\d*.?)/(\\d*?)/(\\d*?)/(\\d*?) *?$", Pattern.CASE_INSENSITIVE);

  private static final Pattern dksRef =
      Pattern.compile("^ *?dksref://(.*?):(\\d*.?)/(\\d*?)/(\\d*?)/(\\d*?)/(\\d*?) *?$", Pattern.CASE_INSENSITIVE);

  /**
   * Constructs a DKSRef from a DKS URL string having the following format:
   * <b>dksref://[IP]:[PORT]/[GROUP_ID]/[NODE_ID]/[UNIQUE_GUID])</b><br>
   * IP can be either an IP number of a hostname,
   * PORT must be an integer (<=65535) describing the port number of the DKS,
   * GROUP_ID must be a number describing the unique DKS ring/group identifier,
   * NODE_ID must be a number describing the node logical identifier,
   * UNIQUE_GUID must be a number describing the node unique identifier (only for identification).
   * The scheme of the url can be in any case, e.g. "dksref://" or "DKSref://".<br><br>
   * Example DKS URL <i>"dksref://localhost:8080/2/431/5156164356"</i>
   * @param dksURL String with the above described format
   * @throws MalformedURLException if the string does not match the above description
   */
  public static DKSRef newDKSRef( String dksURL ) throws MalformedURLException
  {

    if (!dksUrl.matcher(dksURL).matches()) {
      throw new MalformedURLException("Only DKS scheme supported (dksref://<IP>:<PORT>/<GROUP_ID>/<NODE_ID>/<UNIQUE_GUID>)");
    }

    String colons = dksUrl.matcher(dksURL).replaceAll("$1:$2:$3:$4:$5");

    String [] sp = colons.split(":");

    DKSNetAddress netAddress = new DKSNetAddress( sp[0], Integer.parseInt(sp[1]) );
    DKSOverlayAddress overlayAddress = new DKSOverlayAddress( Long.parseLong(sp[3]), Long.parseLong(sp[2]), Long.parseLong(sp[4]) );
    long nonce = Math.abs(new Random().nextLong());
    return new DKSRef(overlayAddress, netAddress, nonce);
  }

  /**
   * Constructs a DKSRef from a DKS URL string having the following format:
   * <b>dks://[IP]:[PORT]/[GROUP_ID]/[NODE_ID]/[UNIQUE_GUID]/[NONCE])</b><br>
   * IP can be either an IP number of a hostname,
   * PORT must be an integer (<=65535) describing the port number of the DKS,
   * GROUP_ID must be a number describing the unique DKS ring/group identifier,
   * NODE_ID must be a number describing the node logical identifier,
   * UNIQUE_GUID must be a number describing the node unique identifier (only for identification).
   * NONCE random id generated once each time a node joins the network
   * The scheme of the url can be in any case, e.g. "dksref://" or "DKSREF://".<br><br>
   * Example DKS URL <i>"dks://localhost:8080/2/431/5156164356/145135314"</i>
   * @param dksURL String with the above described format
   * @throws MalformedURLException if the string does not match the above description
   */
  public static DKSRef valueOf( String dksREF ) throws MalformedURLException
  {
    // TODO use function for the common parts of the two static constructors
    if (!dksRef.matcher(dksREF).matches()) {
      throw new MalformedURLException("Only DKSREF scheme supported (dksref://<IP>:<PORT>/<GROUP_ID>/<NODE_ID>/<UNIQUE_GUID>/<NONCE>)");
    }

    String colons = dksRef.matcher(dksREF).replaceAll("$1:$2:$3:$4:$5:$6");

    String [] sp = colons.split(":");

    DKSNetAddress netAddress = new DKSNetAddress( sp[0], Integer.parseInt(sp[1]) );
    DKSOverlayAddress overlayAddress = new DKSOverlayAddress( Long.parseLong(sp[3]), Long.parseLong(sp[2]), Long.parseLong(sp[4]) );
    long nonce = Math.abs( Long.parseLong(sp[5]) );
    return new DKSRef(overlayAddress, netAddress, nonce);
  }

  /**
   * Creates a DKSRef with references to a DKSOverlayAddress and a DKSNetAddress.
   * Notice, it keeps references and does not make defensive/deep copies
   * @param oa DKSOverlayAddress
   * @param na DKSNetAddress
   */
  public static DKSRef newDKSRefByParts(DKSOverlayAddress oa, DKSNetAddress na) {
    long nonce = Math.abs( new Random().nextLong() );
    return new DKSRef(oa, na, nonce);
  }

  public static DKSRef valueOfByParts(DKSOverlayAddress oa, DKSNetAddress na, long nonce) {
    return new DKSRef(oa, na, nonce);
  }

  private DKSRef(DKSOverlayAddress oa, DKSNetAddress na, long nonce) {
    this.overlayAddress = oa;
    this.netAddress = na;
    this.nonce = Math.abs(nonce);
  }

  /**
   * Constructs a DKSRef from a DKS URL string having the following format:
   * <b>dks://[IP]:[PORT]/[GROUP_ID]/[NODE_ID]/[UNIQUE_GUID])</b><br>
   * IP can be either an IP number of a hostname,
   * PORT must be an integer (<=65535) describing the port number of the DKS,
   * GROUP_ID must be a number describing the unique DKS ring/group identifier,
   * NODE_ID must be a number describing the node logical identifier,
   * UNIQUE_GUID must be a number describing the node unique identifier (only for identification).
   * The scheme of the url can be in any case, e.g. "dks://" or "DKS://".<br><br>
   * Example DKS URL <i>"dks://localhost:8080/2/431/5156164356"</i>
   * @param dksURL String with the above described format
   * @throws MalformedURLException if the string does not match the above description
   */
  private DKSRef( String dksURL ) throws MalformedURLException
  {

    if (!dksUrl.matcher(dksURL).matches()) {
      throw new MalformedURLException("Only DKS scheme supported (dks://<IP>:<PORT>/<GROUP_ID>/<NODE_ID>/<UNIQUE_GUID>)");
    }

    String colons = dksUrl.matcher(dksURL).replaceAll("$1:$2:$3:$4:$5");

    String [] sp = colons.split(":");

    netAddress = new DKSNetAddress( sp[0], Integer.parseInt(sp[1]) );
    overlayAddress = new DKSOverlayAddress( Long.parseLong(sp[3]), Long.parseLong(sp[2]), Long.parseLong(sp[4]) );
  }

  /**
   * Is deprecated, please use the constructor taking a DKS URL string
   * @param newPort int
   * @param newIP String
   * @param newId long
   * @param newGroupId long
   * @param newGUID long
   * @deprecated, the constructor taking a DKS URL should be used instead
   */
  private DKSRef(int newPort, String newIP, long newId, long newGroupId, long newGUID) {
    netAddress = new DKSNetAddress( newIP, newPort );
    overlayAddress = new DKSOverlayAddress( newId, newGroupId, newGUID );
  }

  /**
   * Returns the DKS URL representing this DKSRef with the following format:
   * <b>dks://[IP]:[PORT]/[GROUP_ID]/[NODE_ID]/[UNIQUE_GUID])</b><br>
   * IP can be either an IP number of a hostname,
   * PORT must be an integer (<=65535) describing the port number of the DKS,
   * GROUP_ID must be a number describing the unique DKS ring/group identifier,
   * NODE_ID must be a number describing the node logical identifier,
   * UNIQUE_GUID must be a number describing the node unique identifier (only for identification).
   * The scheme of the url can be in any case, e.g. "dksref://" or "DKSREF://".<br><br>
   * Example DKS URL <i>"dksref://localhost:8080/2/431/5156164356"</i>
   * @return String the DKS URL representing this object
   */
  public String getDKSURL() {
    return "dksref://"+netAddress.getIP()+":"+netAddress.getPort()+"/"+
        overlayAddress.getGroupId()+"/"+overlayAddress.getID()+"/"+
        overlayAddress.getGUID()+"/"+nonce;
  }

  /**
   * Returns the DKS WEB server URL representing this DKSRef with the following format:
   * <b>dks://[IP]:[PORT+1]/[GROUP_ID]/[NODE_ID]/[UNIQUE_GUID])</b><br>
   * IP can be either an IP number of a hostname,
   * PORT must be an integer (<=65535) describing the port number of the DKS,
   * GROUP_ID must be a number describing the unique DKS ring/group identifier,
   * NODE_ID must be a number describing the node logical identifier,
   * UNIQUE_GUID must be a number describing the node unique identifier (only for identification).
   * The scheme of the url can be in any case, e.g. "http://" or "HTTP://".<br><br>
   * Example DKS URL <i>"http://localhost:8080/2/431/5156164356"</i>
   * @return String the DKS URL representing this object
   */
  public String getDKSWebURL() {
    return "http://"+netAddress.getIP()+":"+(netAddress.getPort()+1)+"/info/"+
        overlayAddress.getGroupId()+"/"+overlayAddress.getID()+"/"+
        overlayAddress.getGUID()+"/"+nonce;
  }

  public long getNonce() {
    return nonce;
  }

  public int getPort() {
    return netAddress.getPort();
  }

  public String getIP() {
    return netAddress.getIP();
  }

  public long getID() {
    return overlayAddress.getID();
  }

  public long getGUID() {
    return overlayAddress.getGroupId();
  }

  public long getR_GUID() {
    return overlayAddress.getGUID();
  }

  public DKSOverlayAddress getOverlayAddress() {
    return overlayAddress;
  }

  public DKSNetAddress getDKSNetAddress() {
    return netAddress;
  }

    public String toString(){
	return getDKSURL();
    }

    public boolean equals(Object b){
      if (b==this)
        return true;
      else if (!(b instanceof DKSRef))
        return false;

      DKSRef r = (DKSRef) b;

      return this.overlayAddress.equals(r.overlayAddress) && this.nonce==r.nonce;
    }

    public int hashCode(){
	return this.overlayAddress.hashCode() + 37*(int)(this.nonce ^ (this.nonce >>> 32));
    }
}//DKSRef class
